Skip to content

feat(gui): consolidate Settings and add theme switching#331

Merged
AprilNEA merged 2 commits into
masterfrom
feat/settings-theming
Jun 29, 2026
Merged

feat(gui): consolidate Settings and add theme switching#331
AprilNEA merged 2 commits into
masterfrom
feat/settings-theming

Conversation

@AprilNEA

Copy link
Copy Markdown
Owner

Summary

Folds the standalone About window into Settings and rebuilds the window around a gpui-component-driven theme system.

  • Settings layout — About is now an About page; update controls move to a dedicated Updates page; a new Appearance page hosts the appearance picker, colour-theme grid, corner radius, and language (Language and Theme appear as sidebar sub-items).
  • Theme switchingtheme::palette derives from cx.theme() tokens, so every bespoke surface re-skins with the selected theme. A committed OpenLogi light/dark theme reproduces the previous look; the upstream gpui-component themes are selectable too.
  • macOS-style appearance picker — light / dark / follow-system rendered as three preview thumbnails.
  • Persistence — appearance / theme / radius persist in AppSettings; the macOS titlebar (NSApp appearance) follows the chosen mode.
  • auto_install_updates — opt-in background download + stage, applied on next restart.

Themes via build.rs (not vendored)

Only the OpenLogi brand theme is committed (themes/openlogi.json). The 21 upstream gpui-component themes are not vendored into the repo: build.rs locates gpui-component's source via cargo metadata (robust across the local git cache, CI, and Nix's vendored tree) and embeds its theme files into OUT_DIR. OPENLOGI_THEMES_DIR overrides the lookup.

Refactor / components

  • The Settings page builders are split into per-page submodules under windows/settings/.
  • Reuses gpui-component widgets where they fit: Tag for update-status chips, ButtonGroup for the corner-radius picker. The About link row is a uniform row of small ghost buttons with Lucide icons (changelog/bug vendored under action-icons/, since gpui-component's IconName set lacks them).

Verification

  • cargo clippy --all-targets -- -D warnings (incl. the build script) clean; cargo test -p openlogi-gui green.
  • This branch carries no Nix package files; cargo metadata is expected to resolve the themes dir under Nix, with OPENLOGI_THEMES_DIR as the escape hatch.

Fold the standalone About window into Settings and reorganise the window
around a gpui-component-driven theme system.

- About becomes an About page; update controls move to a dedicated Updates
  page (status hero, check / download&install / restart, the auto-check and
  auto-install toggles, update source).
- New Appearance page: a macOS-style light / dark / follow-system picker
  (three preview thumbnails), a colour-theme grid (the OpenLogi brand theme
  plus the upstream gpui-component themes, filterable by name), corner radius,
  and the interface language. The sidebar shows Language and Theme as
  sub-items.
- theme::palette now derives from cx.theme() tokens, so every bespoke surface
  re-skins with the selected theme. Ship an OpenLogi light/dark theme that
  reproduces the previous look.
- Persist appearance / theme / radius in AppSettings and sync the macOS
  titlebar (NSApp appearance) to the chosen mode.
- auto_install_updates: when opted in, a found update downloads and stages in
  the background, applied on next restart.

Themes: only the OpenLogi brand theme is committed (themes/openlogi.json). The
upstream gpui-component themes are not vendored into the repo — build.rs locates
gpui-component's source via `cargo metadata` (robust across the local git cache,
CI, and Nix's vendored tree) and embeds its theme files into OUT_DIR. Set
OPENLOGI_THEMES_DIR to override the lookup.

Cards use the Outline group-box variant for definition. The Settings page
builders are split into per-page submodules under windows/settings/ (general,
updates, permissions, appearance, language, assets, about), leaving the view +
window plumbing in settings.rs. Update-status chips use Tag and the
corner-radius picker uses ButtonGroup; the About page links are a uniform row of
small ghost buttons with Lucide icons (changelog and bug vendored under
action-icons/, since gpui-component's IconName set lacks them).
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown

Greptile Summary

This PR consolidates the standalone About window into the Settings panel and introduces a gpui-component-driven theme system. Settings is refactored into per-page submodules, and the Appearance page exposes a light/dark mode picker, theme grid, corner-radius selector, and language chooser. Persistence (appearance, theme slots, radius) lands in AppSettings and survives restarts.

  • Appearance/theme switchingapply_from_settings becomes the single entry point for first paint, OS-appearance observers, and live settings edits; it hydrates both theme slots from the registry and applies any radius override after Theme::change so it isn't clobbered by the theme's own value.
  • build.rs theme embedding — Upstream gpui-component themes are located at build time via cargo metadata and embedded via include_str!/OUT_DIR, keeping 21 theme JSON files out of the repo while remaining robust across local git caches, CI, and Nix-vendored trees.
  • Auto-install updates — A new auto_install_updates preference wires a GPUI observer to Updater; a found update downloads and stages in the background, applied on next restart.

Confidence Score: 5/5

Safe to merge — all changes are additive UI/UX improvements with no mutations to persisted device config, no schema-breaking changes (all new fields carry serde(default)), and the two prior blocking issues from review round 1 are correctly resolved.

The core logic — theme application, persistence, OS appearance sync, auto-install observer — is well-guarded: radius override is applied after Theme::change so it isn't clobbered, the auto-install observer guards on Available to avoid re-entry, and the Follow-System check in theme_card prevents wiping the mode preference. The remaining observations are display cosmetics and a maintenance brittleness in the deep-link page index, none of which affect correctness at runtime.

crates/openlogi-gui/src/windows/settings/updates.rs (download progress display and hardcoded URL) and crates/openlogi-gui/src/windows/settings.rs (hardcoded deep-link page indices).

Important Files Changed

Filename Overview
crates/openlogi-gui/build.rs New build script: locates gpui-component themes via cargo metadata JSON scanning and embeds them into OUT_DIR; includes an OPENLOGI_THEMES_DIR escape hatch. The manifest_paths parser assumes no JSON-escaped characters in paths (macOS/Linux only, acknowledged).
crates/openlogi-gui/src/theme.rs Replaces hand-painted Palette variants with tokens derived from cx.theme(); adds register_builtin_themes and apply_from_settings as the sole theme-change entry point. Radius override is correctly applied after Theme::change to avoid being reset by the theme config.
crates/openlogi-gui/src/windows/settings/appearance.rs New Appearance settings page with mode picker thumbnails, theme grid with filter/search, radius ButtonGroup, and language selector. Previous issues with Follow-System mode override and radius hardcoding are correctly addressed.
crates/openlogi-gui/src/windows/settings/updates.rs New Updates settings page with hero card, status pill, check/install/restart action, and auto-install toggle. Integer truncation in the download progress display shows "0 MB" for sub-megabyte downloads; the source URL label is also hardcoded independently of RELEASES_URL.
crates/openlogi-gui/src/windows/settings.rs Refactored into a thin coordinator that re-exports shared imports and delegates each page to a submodule. The SettingsPage::About hardcoded index (5) must stay in sync with the page chain order manually; a mismatch would silently navigate to the wrong page.
crates/openlogi-gui/src/platform/updater.rs Adds AutoInstaller global holding the auto-install observer alive; fires download_and_install when the updater enters Available status and the setting is on. The condition guard on Available prevents re-entry once downloading.
crates/openlogi-core/src/config.rs Adds Appearance enum, auto_install_updates, theme_light/dark, and ui_radius to AppSettings with serde(default) for backward compatibility. Adds has_app_override helper.
crates/openlogi-gui/src/windows/settings/about.rs New About settings page with hero card, build identity, link buttons using custom action icons, diagnostics copy with generation-guarded 2s reset timer, and config-file reveal button.
crates/openlogi-gui/src/state.rs Adds set_auto_install_updates, set_appearance, set_theme, and set_ui_radius with no-op guards on unchanged values and atomic config persistence.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant SettingsView
    participant AppState
    participant theme_apply as theme::apply_from_settings
    participant ThemeRegistry
    participant Theme
    participant OS as platform::os

    User->>SettingsView: click mode card / theme card / radius btn
    SettingsView->>AppState: set_appearance / set_theme / set_ui_radius
    AppState->>AppState: save_atomic (persist to disk)
    SettingsView->>theme_apply: apply_from_settings(None, cx)
    theme_apply->>AppState: read appearance, theme_light, theme_dark, ui_radius
    theme_apply->>OS: set_app_appearance(appearance)
    theme_apply->>theme_apply: cx.window_appearance() → os_appearance
    theme_apply->>ThemeRegistry: pick light and dark theme configs
    theme_apply->>Theme: set light_theme / dark_theme
    theme_apply->>Theme: Theme::change(mode, None, cx)
    Note over Theme: resets radius to theme default
    theme_apply->>Theme: "global_mut(cx).radius = user override (if any)"
    theme_apply->>SettingsView: cx.refresh_windows()

    Note over User,OS: OS appearance change (System mode)
    OS-->>SettingsView: observe_window_appearance fires
    SettingsView->>theme_apply: apply_from_settings(Some(window), cx)
    theme_apply->>Theme: Theme::change(ThemeMode::from(os), Some(window), cx)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant SettingsView
    participant AppState
    participant theme_apply as theme::apply_from_settings
    participant ThemeRegistry
    participant Theme
    participant OS as platform::os

    User->>SettingsView: click mode card / theme card / radius btn
    SettingsView->>AppState: set_appearance / set_theme / set_ui_radius
    AppState->>AppState: save_atomic (persist to disk)
    SettingsView->>theme_apply: apply_from_settings(None, cx)
    theme_apply->>AppState: read appearance, theme_light, theme_dark, ui_radius
    theme_apply->>OS: set_app_appearance(appearance)
    theme_apply->>theme_apply: cx.window_appearance() → os_appearance
    theme_apply->>ThemeRegistry: pick light and dark theme configs
    theme_apply->>Theme: set light_theme / dark_theme
    theme_apply->>Theme: Theme::change(mode, None, cx)
    Note over Theme: resets radius to theme default
    theme_apply->>Theme: "global_mut(cx).radius = user override (if any)"
    theme_apply->>SettingsView: cx.refresh_windows()

    Note over User,OS: OS appearance change (System mode)
    OS-->>SettingsView: observe_window_appearance fires
    SettingsView->>theme_apply: apply_from_settings(Some(window), cx)
    theme_apply->>Theme: Theme::change(ThemeMode::from(os), Some(window), cx)
Loading

Reviews (2): Last reviewed commit: "fix(gui): gate per-platform Settings imp..." | Re-trigger Greptile

Comment thread crates/openlogi-gui/src/windows/settings/appearance.rs
Comment thread crates/openlogi-gui/src/windows/settings/appearance.rs
- permissions.rs: the page split left `Permission` / `PermissionStatus` and the
  macOS-only helpers imported unconditionally, breaking the Linux/Windows builds
  (E0432 unresolved import). Split the imports into always / macOS+Linux /
  macOS-only groups matching their actual use.
- Appearance: clicking a theme card no longer clobbers a "Follow System" mode
  preference — the mode is only pinned when the user already chose an explicit
  Light/Dark mode (configuring the dark slot must not force the whole app dark).
- Appearance: the corner-radius "Default" entry now stores `None` (defer to the
  theme's own radius) instead of a fixed 6px, fixing the mis-highlight under
  themes with a different radius and the one-way trap away from the theme value.
@AprilNEA AprilNEA merged commit 4d3dad4 into master Jun 29, 2026
9 checks passed
@AprilNEA AprilNEA deleted the feat/settings-theming branch June 29, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant